package com.sijobe.installer;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.channels.FileChannel;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import javax.swing.UIManager;

/**
 * Contains miscellaneous utility methods
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author simo_415
 */
public class Util {
   public static final int BUFFER = 4096; // 4KB
   public static File INSTALLBASE;
   public static final String CLASSNAME = "Util.class";

   /**
    * Gets the File location of where Minecraft is installed
    * 
    * @return The File object of where the Minecraft installation is
    */
   public static File getMinecraftDir() {
      File dir = null;
      String sys = System.getProperty("user.home", ".");
      String os = System.getProperty("os.name").toLowerCase();
      if (os.contains("win")) {
         String data = System.getenv("APPDATA");
         if (data != null) {
            dir = new File(data, ".minecraft/");
         } else {
            dir = new File(sys, ".minecraft/");
         }
      } else if (os.contains("mac")) {
         dir = new File(sys, "Library/Application Support/minecraft");
      } else if (os.contains("solaris")) {
         dir = new File(sys, ".minecraft/");
      } else {
         dir = new File(sys, "minecraft/");
         if (!dir.exists() || !dir.isDirectory()) {
            dir = new File(sys,".minecraft/");
         }
      }
      if (dir == null || !dir.exists() || !dir.isDirectory()) {
         return null;
      }
      return dir;
   }

   /**
    * Gets all the Files which are in the same location as this class. This will find where the
    * current class exists (whether this is in a JAR or directory doesn't matter). Then at the 
    * specified location it will grab a list of files.
    * 
    * @return A list of Files is returned which are in the same relative location as this class
    */
   public static List getFilesExceptCurrent() {
      String test = Util.class.getResource(CLASSNAME).toString();
      File base = null;
      if (test.startsWith("jar")) {
         String jarfile = test.replaceAll("jar:", "").split("!")[0];
         try {
            File extract = getTempDirectory();
            if (extractArchive(new File((new URL(jarfile)).toURI()),extract)) {
               base = extract;
            } else {
               return null;
            }
         } catch (Exception e) {
            e.printStackTrace();
            return null;
         }
      } else {
         String classLocation = Util.class.getName().replace('.', '/') + ".class";
         String packagename = Util.class.getPackage().getName();
         int parents = 0;
         try {
            packagename = Util.class.getPackage().getName();
            parents = packagename.split("\\.").length;
         } catch (Exception e) { }
         URLClassLoader loader = (URLClassLoader) Util.class.getClassLoader();
         URL u = loader.getResource(classLocation);
         File f = (new File(u.getFile())).getParentFile();
         try {
            if (f == null || !f.exists()) {
               f = (new File(u.toURI())).getParentFile();
            }
         } catch (Exception e) {
            return null;
         }
         for (int i = 0; i < parents; i++) {
            f = f.getParentFile();
         }
         if (f == null || !f.exists()) {
            return null;
         }
         base = f;
      }
      if (base == null || !base.isDirectory()) {
         return null;
      }
      List temp = getFiles(base);
      Iterator i = temp.iterator();
      while (i.hasNext()) {
         File tempfile = (File)i.next();
         if (tempfile.getName().equalsIgnoreCase(CLASSNAME)) {
            temp.remove(tempfile);
            break;
         }
      }
      INSTALLBASE = base;
      return temp;
   }

   /**
    * Gets a list of files which are in the specified directory
    * 
    * @param directory - The directory to get the files from
    * @return A list of files at the specified directory
    */
   public static List getFiles(File directory) {
      if (directory == null || !directory.exists() || !directory.isDirectory()) {
         return null;
      }
      File files[] = directory.listFiles();
      List thelist = new Vector();
      for (int i = 0; i < files.length; i++) {
         File file = files[i];
         thelist.add(file);
      }
      return thelist;
   }

   /**
    * Gets a list of all files which are in the specified directory as well as sub-directories
    * 
    * @param directory - The directory to get the files from
    * @return A list of files at the specified directory
    */
   public static List getAllFiles(File directory) {
      if (directory == null || !directory.exists() || !directory.isDirectory()) {
         return null;
      }
      File files[] = directory.listFiles();
      List thelist = new Vector();
      for (int i = 0; i < files.length; i++) {
         File file = files[i];
         if (file.isDirectory()) {
            thelist.add(file);
            thelist.addAll(getAllFiles(file));
         } else {
            thelist.add(file);
         }
      }
      return thelist;
   }

   /**
    * Creates a temporary directory. The directory location is then returned to the user
    * 
    * @return The location of the temporary directory
    */
   public static File getTempDirectory() {
      File temp;
      try {
         temp = File.createTempFile("mcmodinst", System.currentTimeMillis() + "");
         if(!(temp.delete())) {
            throw new IOException("Temp file not deleted: " + temp.getAbsolutePath());
         }
         if(!(temp.mkdir())) {
            throw new IOException("Temp directory not created: " + temp.getAbsolutePath());
         }
      } catch (Exception e) {
         temp = null;
         e.printStackTrace();
      }
      return temp;
   }

   /**
    * Extracts the given archive into the specified location
    * 
    * @param archive - The archive to extract
    * @param destination - The destination which the archive is to be extracted to
    * @return True is returned when the archive was able to successfully able to
    * be extracted.
    */
   public static boolean extractArchive(File archive, File destination) {
      if (archive == null || destination == null) {
         return false;
      }
      System.out.println("Extracting " + archive.getName());
      try {
         destination.mkdirs();
      } catch (Exception e) {
         return false;
      }
      ZipFile zf = null;
      try {
         zf = new ZipFile(archive);
      } catch (Exception e) {
         e.printStackTrace();
         return false;
      }
      try {
         Enumeration e = zf.entries();
         while (e.hasMoreElements()) {
            ZipEntry ze = (ZipEntry)e.nextElement();
            File dest = new File(destination, ze.getName());
            dest.getParentFile().mkdirs();
            if (!ze.isDirectory()) {
               BufferedInputStream is = new BufferedInputStream(zf.getInputStream(ze));
               int currentByte;
               byte data[] = new byte[BUFFER];

               FileOutputStream fos = new FileOutputStream(dest);
               BufferedOutputStream des = new BufferedOutputStream(fos, BUFFER);

               while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
                  des.write(data, 0, currentByte);
               }
               des.flush();
               des.close();
               is.close();
            }
         }
      } catch (Exception e) {
         e.printStackTrace();
         return false;
      }
      return true;
   }

   /**
    * Compresses the specified files to the specified destination
    * 
    * @param source - The directory to compress into an archive
    * @param destination - The destination archive
    * @return True is returned when the files were able to be compressed successfully
    */
   public static boolean compressFiles(File source, File destination) {
      if (source == null || destination == null) {
         return false;
      }
      System.out.println("Compressing " + destination.getName());
      try {
         URI base = source.toURI();
         BufferedInputStream origin = null;
         FileOutputStream dest = new FileOutputStream(destination);
         ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
         byte data[] = new byte[BUFFER];
         List file = null;
         if (source.isDirectory()) {
            file = getAllFiles(source);
         } else if (source.isFile()) {
            file = (new Vector());
            file.add(source);
         }
         Iterator i = file.iterator();

         while (i.hasNext()) {
            File next = (File)i.next();
            if (next.isDirectory()) {
               ZipEntry entry = new ZipEntry(base.relativize(next.toURI()).getPath());
               out.putNextEntry(entry);
               continue;
            }
            origin = new BufferedInputStream(new FileInputStream(next), BUFFER);
            ZipEntry entry = new ZipEntry(base.relativize(next.toURI()).getPath());
            out.putNextEntry(entry);
            int count;
            while((count = origin.read(data, 0, BUFFER)) != -1) {
               out.write(data, 0, count);
            }
            origin.close();
         }
         out.close();
      } catch(Exception e) {
         System.out.println("ERROR: " + e.getMessage());
         e.printStackTrace();
         return false;
      }
      return true;
   }

   /**
    * Copies a directory from one location to another
    * 
    * @param from The origin directory - where the contents are copied from
    * @param to - The destination directory
    * @return Returns true if the copy was successful
    */
   public static boolean copyDirectory(File from, File to) {
      System.out.println("Copying directory " + from.getName());
      if (from == null || to == null || !from.isDirectory()) {
         System.out.println("ERROR: Specified file is not a directory.");
         return false;
      }
      if (!to.exists()) {
         to.mkdirs();
      }
      if (!to.isDirectory()) {
         System.out.println("ERROR: Destination is not a directory.");
         return false;
      }
      try {
         File list[] = from.listFiles();
         for (int i = 0; i < list.length; i++) {
            if (list[i].isDirectory()) {
               copyDirectory(list[i], new File(to, list[i].getName()));
            } else if (list[i].isFile()) {
               copyFile(list[i], new File(to, list[i].getName()));
            }
         }
      } catch (Exception e) {
         e.printStackTrace();
         System.out.println("ERROR: " + e.getMessage());
         return false;
      }
      return true;
   }

   /**
    * Copies a single file from one location to another
    * 
    * @param from The origin file
    * @param to The destination file
    * @return Returns true if the file was able to be copied successfully
    */
   public static boolean copyFile(File sourceFile, File destFile) {
      System.out.println("Copying " + sourceFile.getName());
      if (!destFile.exists()) {
         try {
            destFile.getParentFile().mkdirs();
            destFile.createNewFile();
         } catch (IOException e) {
            e.printStackTrace();
            System.out.println("ERROR (1): Could not copy " + destFile.getAbsolutePath());
            return false;
         }
      }

      FileChannel source = null;
      FileChannel destination = null;
      try {
         source = new FileInputStream(sourceFile).getChannel();
         destination = new FileOutputStream(destFile).getChannel();
         destination.transferFrom(source, 0, source.size());
      } catch (Exception e) {
         e.printStackTrace();
         System.out.println("ERROR (2): Could not copy " + destFile.getAbsolutePath());
         return false;
      } finally {
         try {
            if (source != null) {
               source.close();
            }
            if (destination != null) {
               destination.close();
            }
         } catch (Exception e) {
         }
      }
      return true;
   }

   /**
    * Recursively deletes the specified file or folder tree
    * 
    * @param file - The file or folder to delete
    */
   public static void delete(File file) {
      if (file == null || !file.exists()) {
         return;
      } else if (file.isFile()) {
         file.delete();
         return;
      }
      File children[] = file.listFiles();
      for (int i = 0; i < children.length; i++) {
         File child = children[i];
         if (child.isFile()) {
            child.delete();
         } else {
            delete(child);
         }
      }
      file.delete();
   }

   /**
    * Reads a file and returns the contents (assumes that file is ASCII)
    * 
    * @param filename - The file to read
    * @return The data which is contained in the file
    */
   public static String readFile(File filename) {
      if (filename == null) {
         return "";
      }
      String content = "";
      BufferedReader in = null;
      try {
         in = new BufferedReader(new FileReader(filename));
         String line = null;
         while ((line = in.readLine()) != null) {
            content += line + "\n";
         }
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         if (in != null) {
            try {
               in.close();
            } catch (Exception e) { }
         }
      }
      return content;
   }

   /**
    * Scales an image to another size
    * @param src - The image source
    * @param w - The new width
    * @param h - The new height
    * @return The image at the new dimensions
    */
   public static BufferedImage scale(BufferedImage src, int w, int h) {  
      int type = BufferedImage.TYPE_INT_RGB;  
      BufferedImage dst = new BufferedImage(w, h, type);  
      Graphics2D g2 = dst.createGraphics();   
      g2.setBackground(UIManager.getColor("Panel.background"));  
      g2.clearRect(0,0,w,h);  
      double xScale = (double)w/src.getWidth();  
      double yScale = (double)h/src.getHeight();  
      double scale = Math.min(xScale, yScale);  
      int width  = (int)(scale*src.getWidth());  
      int height = (int)(scale*src.getHeight());  
      int x = (w - width)/2;  
      int y = (h - height)/2;  
      g2.drawImage(src, x, y, width, height, null);  
      g2.dispose();  
      return dst;  
   } 

   /**
    * Returns the first instance of a File within the List which in which has 
    * the filename specified by the "name" parameter
    * @param name - The name of the file
    * @param files - The list of files
    * @return The File which has that name
    */
   public static File getFile(String name, List files) {
      for(int i = 0; i < files.size(); i++) {
         File f = (File)files.get(i);
         System.out.println("DEBUG: " + f.getName());
         if (f.getName().equalsIgnoreCase(name)) {
            return f;
         }
      }
      return null;
   }

   /**
    * Adds the specified file to the classpath dynamically
    * 
    * @param file - The file to add to the classpath
    * @return True if the file was added, false otherwise.
    */
   public static boolean addToClasspath(File file) {
      return addToClasspath(file,(URLClassLoader) ClassLoader.getSystemClassLoader());
   }
   
   /**
    * Adds the specified file to the classpath dynamically
    * 
    * @param file - The file to add to the classpath
    * @param loader - The classloader to add the file to
    * @return True if the file was added, false otherwise.
    */
   public static boolean addToClasspath(File file, URLClassLoader loader) {
      if (!file.exists()) {
         return false;
      }
      Class/*<URLClassLoader>*/ sysclass = URLClassLoader.class;
      try {
         Method method = sysclass.getDeclaredMethod("addURL", new Class[] { URL.class });
         method.setAccessible(true);
         method.invoke(loader, new Object[] { file.toURI().toURL() });
      } catch (Throwable t) {
         t.printStackTrace();
         return false;
      }
      return true;
   }

   /**
    * Gets the version of Minecraft which you are about to install over
    * 
    * @return A String containing the current Minecraft version
    */
   public static String getMinecraftVersion(String mcdir) {
      // Find Minecraft.jar and lwjgl.jar
      File mcjar = new File(mcdir,"bin/minecraft.jar");
      File lwjgl = new File(mcdir,"bin/lwjgl.jar");
      if (!mcjar.exists() || !lwjgl.exists()) {
         return null;
      }
      
      // Create separate classloader to load instance of Minecraft jar's into
      URLClassLoader loader = null;
      Class minecraft = null;
      try {
         loader = new URLClassLoader(new URL[] {mcjar.toURI().toURL(), lwjgl.toURI().toURL()},null);
         for (int i = 0; i < loader.getURLs().length; i++) {
            System.out.println(loader.getURLs().toString());
         }
         //minecraft = loader.loadClass("net.minecraft.client.Minecraft");
         minecraft = Class.forName("net.minecraft.client.Minecraft",false,loader);
      } catch (Throwable e) {
         e.printStackTrace();
         return null;
      }

      try {            
         // Find the correct method (regardless of name) then invoke the method
         Method methods[] = minecraft.getMethods();
         for (int i = 0; i < methods.length; i++) {
            // Check return type is String
            if (methods[i].getReturnType() != String.class) {
               continue;
            }
            // Check parameter type is empty
            if (methods[i].getParameterTypes().length != 0) {
               continue;
            }
            // Check method type is static
            if (Modifier.isStatic(methods[i].getModifiers())) {
               return (String)methods[i].invoke(null, null);
            }
         }
      } catch (Throwable t) {
         t.printStackTrace();
         return null;
      }
      return null;
   }
}
